package com.squareup.anvil.annotations

import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.CLASS
import kotlin.reflect.KClass

/**
 * Similar to [MergeSubcomponent], but with the main difference that the subcomponent is generated
 * and modules, bindings and component interfaces are merged whenever the component with
 * [parentScope] is processed.
 *
 * Imagine this module dependency tree:
 * ```
 *        :app
 *       /     \
 *      v       v
 *   :lib-a   :lib-b
 * ```
 * `:app` creates the component with `@MergeComponent`, `:lib-a` creates a subcomponent with
 * `@MergeSubcomponent` and `:lib-b` contributes a module to the scope of the subcomponent. This
 * module won't be included in the subcomponent without adding the dependency `:lib-a -> :lib-b`.
 * See [MergeSubcomponent] for further explanation.
 *
 * On the other hand, if `:lib-a` uses `@ContributesSubcomponent` with a parent scope of the main
 * component from `:app`, then the actual subcomponent will be generated in the `:app` module and
 * the contributed module from `:lib-b` will be picked up.
 *
 * ```
 * @ContributesSubcomponent(
 *   scope = ActivityScope::class,
 *   parentScope = AppScope::class
 * )
 * interface ActivitySubcomponent
 * ```
 *
 * [parentScope] can be the scope of a [MergeComponent], [MergeSubcomponent] or another
 * [ContributesSubcomponent]. A chain of [ContributesSubcomponent]s is supported.
 *
 * It's possible to exclude any automatically added Dagger module or component interface with the
 * [exclude] parameter if needed.
 * ```
 * @ContributesSubcomponent(
 *   scope = ActivityScope::class,
 *   parentScope = AppScope::class,
 *   exclude = [
 *     DaggerModule::class,
 *     ComponentInterface::class
 *   ]
 * )
 * interface ActivitySubcomponent
 * ```
 *
 * It's recommended to define a parent component interface as inner class of the contributed
 * subcomponent that is contributed to the parent scope. Anvil will always generate a parent
 * component interface for the final generated subcomponent with an abstract factory method for
 * the subcomponent. If you create your own parent component interface, then the one generated by
 * Anvil will extend your interface. E.g.
 * ```
 * @ContributesSubcomponent(
 *   scope = ActivityScope::class,
 *   parentScope = AppScope::class
 * )
 * interface ActivitySubcomponent {
 *
 *   @ContributesTo(AppScope::class)
 *   interface ParentComponent {
 *     fun createActivitySubcomponent(): ActivitySubcomponent
 *   }
 * }
 * ```
 */
@Target(CLASS)
@Retention(RUNTIME)
public annotation class ContributesSubcomponent(
  /**
   * The scope used to find all contributed bindings, multibindings, modules and component
   * interfaces, which should be included in this subcomponent.
   */
  val scope: KClass<*>,

  /**
   * The actual subcomponent for this contributed subcomponent will be generated when a
   * [MergeComponent], [MergeSubcomponent] or another [ContributesSubcomponent] annotated component
   * with the same scope as `parentScope` is merged.
   */
  val parentScope: KClass<*>,
  /**
   * List of Dagger modules that should be manually included in the subcomponent and aren't
   * automatically contributed.
   */
  val modules: Array<KClass<*>> = [],
  /**
   * List of bindings, multibindings, modules and component interfaces that are contributed to the
   * same scope, but should be excluded from the subcomponent.
   */
  val exclude: Array<KClass<*>> = [],
  /**
   * This contributed subcomponent will replace these contributed subcomponents. All replaced
   * subcomponents must use the same scope.
   */
  val replaces: Array<KClass<*>> = [],
) {
  /**
   * A factory for a contributed subcomponent.
   *
   * This follows all the rules of `Subcomponent.Factory`, except it must appear in classes
   * annotated with `@ContributesSubcomponent` instead of `@Subcomponent`.
   *
   * It's strongly recommended creating a parent component interface next to the factory that is
   * contributed to the parent scope. The parent component interface should have one method that
   * returns the factory type, e.g.
   * ```
   * @ContributesSubcomponent(
   *   scope = ActivityScope::class,
   *   parentScope = AppScope::class
   * )
   * interface ActivitySubcomponent {
   *
   *   @ContributesSubcomponent.Factory
   *   interface Factory {
   *     fun createActivitySubcomponent(
   *       @BindsInstance int: Int
   *     ): ActivitySubcomponent
   *   }
   *
   *   @ContributesTo(AppScope::class)
   *   interface ParentComponent {
   *     fun createActivitySubcomponentFactory(): ActivitySubcomponent.Factory
   *   }
   * }
   * ```
   * If no parent component interface but a factory is present, then the automatically generated
   * parent component interface will return a type of the factory and not the subcomponent
   * directly.
   */
  public annotation class Factory
}
